package ualrcdg.shared;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.codec.binary.Base64OutputStream;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ualrcdg.shared.dto.Character;
import ualrcdg.shared.dto.ChunkMedia;
import ualrcdg.shared.dto.FileMedia;
import ualrcdg.shared.dto.Game;
import ualrcdg.shared.dto.MediaScript;
import ualrcdg.shared.dto.MediaScriptEntry;
import ualrcdg.shared.dto.Page;
import ualrcdg.shared.dto.PropertyUpdate;
import ualrcdg.shared.dto.PropertyUpdateSet;
import ualrcdg.shared.dto.Rule;
import ualrcdg.shared.dto.RuleSet;
import ualrcdg.shared.dto.SimpleProperty;
import ualrcdg.shared.dto.Task;
import ualrcdg.shared.dto.TaskFSM;
import ualrcdg.shared.exceptions.CDGException;
import ualrcdg.shared.exceptions.CDGParseException;
import ualrcdg.shared.exceptions.CDGTypeException;
import ualrcdg.shared.requests.AuthenticatePlayerRequest;
import ualrcdg.shared.requests.CDGRequest;
import ualrcdg.shared.requests.FetchGameRequest;
import ualrcdg.shared.requests.GameListRequest;
import ualrcdg.shared.requests.RegisterPlayerRequest;
import ualrcdg.shared.requests.UpdateProgressRequest;
import ualrcdg.shared.requests.ValidateRuleRequest;
import ualrcdg.shared.responses.AuthenticatePlayerResponse;
import ualrcdg.shared.responses.CDGResponse;
import ualrcdg.shared.responses.FetchGameResponse;
import ualrcdg.shared.responses.GameHeader;
import ualrcdg.shared.responses.GameListResponse;
import ualrcdg.shared.responses.RegisterPlayerResponse;
import ualrcdg.shared.responses.UpdateProgressResponse;
import ualrcdg.shared.responses.ValidateRuleResponse;

/**
 *  Utility class that encapsulates all CDG-specific DOM/XML parsing and writing methods.
 */
public class CDGXMLHelper extends XMLHelper
{
	private static final String whiteSpacePattern = "\\s+";
	private static final String stateSplitPattern = "\\s*" + "\\." + "\\s*";

	private static final String newline = System.getProperty( "line.separator" );
	
	//LATER Remove after testing is complete
	public static void test1( ) throws TransformerConfigurationException, TransformerException, CDGParseException
	{
		Document[ ] docs = new Document[ ]
		{
			createAuthenticatePlayerRequestDocument( new AuthenticatePlayerRequest( "req1", "un1", "pw1" ) ),
			createFetchGameRequestDocument( new FetchGameRequest( "req2", "game2" ) ),
			createGameListRequestDocument( new GameListRequest( "req3" ) ),
			createRegisterPlayerRequestDocument( new RegisterPlayerRequest( "req4", "un4", "pw4" ) ),
			createUpdateProgressRequestDocument( new UpdateProgressRequest( "req5", "un5", "pw5", "game5", "task5" ) ), 
			createValidateRuleRequestDocument( new ValidateRuleRequest( "req6", "type6", "parm6:value6", "data6" ) )
		};
		
		CDGRequest[] reqs = new CDGRequest[]
		{
			parseAuthenticatePlayerRequestDocument( docs[0] ),
			parseFetchGameRequestDocument( docs[1] ),
			parseGameListRequestDocument( docs[2] ),
			parseRegisterPlayerRequestDocument( docs[3] ), 
			parseUpdateProgressRequestDocument( docs[4] ),  
			parseValidateRuleRequestDocument( docs[5] )
		};
		
		Document[ ] redocs = new Document[ ]
		{
			createAuthenticatePlayerRequestDocument( ( AuthenticatePlayerRequest ) reqs[0] ), 
			createFetchGameRequestDocument( ( FetchGameRequest ) reqs[1] ),
			createGameListRequestDocument( ( GameListRequest ) reqs[2] ),
			createRegisterPlayerRequestDocument( ( RegisterPlayerRequest ) reqs[3] ), 
			createUpdateProgressRequestDocument( ( UpdateProgressRequest ) reqs[4] ),  
			createValidateRuleRequestDocument( ( ValidateRuleRequest ) reqs[5] )
		};
		
		Transformer transformer = transformerFactory.newTransformer( );
		transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );

		for( int i = 0; i < docs.length; i++ )
		{
			Document doc = docs[i];
			DOMSource dom = new DOMSource( doc );
			StringWriter writer = new StringWriter( );
			StreamResult result = new StreamResult( writer );

			DocumentType docType = doc.getDoctype( );

			if( docType != null )
			{
				transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, docType.getSystemId( ) );
			}
			
			
			transformer.transform( dom, result );
			
			System.out.println( "==========================================================================" );
			System.out.println( writer.toString( ) );
			System.out.println( "==========================================================================" );
			System.out.println( reqs[i].toString( ) );
			System.out.println( "==========================================================================" );

			Document doc1 = redocs[i];
			DOMSource dom1 = new DOMSource( doc1 );
			StringWriter writer1 = new StringWriter( );
			StreamResult result1 = new StreamResult( writer1 );

			DocumentType docType1 = doc1.getDoctype( );
			if( docType1 != null )
			{
				transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, docType1.getSystemId( ) );
			}

			transformer.transform( dom1, result1 );
			
			System.out.println( writer1.toString( ) );
			System.out.println( "==========================================================================" );
			System.out.println( );
			System.out.println( );
		}
	}

	//LATER Remove after testing is complete
	public static void test2( ) throws TransformerConfigurationException, TransformerException, CDGParseException
	{
		ArrayList< String > fileList = new ArrayList< String >( );
		fileList.add( "file1" );
		fileList.add( "file2" );
		
		ArrayList< GameHeader > gameList = new ArrayList< GameHeader >( );
		gameList.add( new GameHeader( "id1", "name1", "desc1" ) );
		gameList.add( new GameHeader( "id2", "name2", "desc2" ) );
		gameList.add( new GameHeader( "id3", "name3", "desc3" ) );
		
		Document[ ] docs = new Document[ ]
		{
			createAuthenticatePlayerResponseDocument( new AuthenticatePlayerResponse( "req1", false ) ),
			createFetchGameResponseDocument( new FetchGameResponse( "req2", fileList ) ),
			createGameListResponseDocument( new GameListResponse( "req3", gameList ) ),
			createRegisterPlayerResponseDocument( new RegisterPlayerResponse( "req4", true ) ),
			createUpdateProgressResponseDocument( new UpdateProgressResponse( "req5", false ) ), 
			createValidateRuleResponseDocument( new ValidateRuleResponse( "req6", true ) )
		};
		
		CDGResponse[] resps = new CDGResponse[]
		{
			parseAuthenticatePlayerResponseDocument( docs[0] ),
			parseFetchGameResponseDocument( docs[1] ),
			parseGameListResponseDocument( docs[2] ),
			parseRegisterPlayerResponseDocument( docs[3] ), 
			parseUpdateProgressResponseDocument( docs[4] ),  
			parseValidateRuleResponseDocument( docs[5] )
		};
		
		Document[ ] redocs = new Document[ ]
		{
			createAuthenticatePlayerResponseDocument( ( AuthenticatePlayerResponse ) resps[0] ), 
			createFetchGameResponseDocument( ( FetchGameResponse ) resps[1] ),
			createGameListResponseDocument( ( GameListResponse ) resps[2] ),
			createRegisterPlayerResponseDocument( ( RegisterPlayerResponse ) resps[3] ), 
			createUpdateProgressResponseDocument( ( UpdateProgressResponse ) resps[4] ),  
			createValidateRuleResponseDocument( ( ValidateRuleResponse ) resps[5] )
		};
				
		
		Transformer transformer = transformerFactory.newTransformer( );
		transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );

		for( int i = 0; i < docs.length; i++ )
		{
			Document doc = docs[i];
			DOMSource dom = new DOMSource( doc );
			StringWriter writer = new StringWriter( );
			StreamResult result = new StreamResult( writer );

			DocumentType docType = doc.getDoctype( );

			if( docType != null )
			{
				transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, docType.getSystemId( ) );
			}
			
			
			transformer.transform( dom, result );
			
			System.out.println( "==========================================================================" );
			System.out.println( writer.toString( ) );
			System.out.println( "==========================================================================" );
			System.out.println( resps[i].toString( ) );
			System.out.println( "==========================================================================" );

			Document doc1 = redocs[i];
			DOMSource dom1 = new DOMSource( doc1 );
			StringWriter writer1 = new StringWriter( );
			StreamResult result1 = new StreamResult( writer1 );

			DocumentType docType1 = doc1.getDoctype( );
			if( docType1 != null )
			{
				transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, docType1.getSystemId( ) );
			}

			transformer.transform( dom1, result1 );
			
			System.out.println( writer1.toString( ) );
			System.out.println( "==========================================================================" );
			System.out.println( );
			System.out.println( );
		}
	}
	
	static final String GAMEXML = 
			"<!DOCTYPE game SYSTEM \"http://www.ualrcdg.com/game.dtd\">"+
			"<game name=\"test name\" description=\"test description\" rootTask=\"task0\">"+
			"	<character id=\"character0\">"+
			"		<properties>"+
			"			<simpleProperty id=\"property0\" name=\"property0\" type=\"Boolean\" initialValue=\"false\" />"+
			"			<simpleProperty id=\"property1\" name=\"property1\" type=\"Integer\" initialValue=\"-1\" />"+
			"		</properties>"+
			"	</character>"+
			"	<tasks>"+
			"		<task id=\"task0\" name=\"task0\" description=\"task0\" preMediaScriptId=\"script0\" ruleId=\"taskfsm0\" postMediaScriptId=\"script0\" />"+
			"		<task id=\"task1\" name=\"task1\" description=\"task1\" preMediaScriptId=\"script1\" ruleId=\"rule0\" postMediaScriptId=\"script1\" />"+
			"		<taskFSM id=\"taskfsm0\" name=\"taskfsm0\" stateCount=\"3\" initialState=\"s0\" acceptingStates=\"s1\">"+
			"			s0: task0 ~ s1; task1 ~ s2."+
			"			s1: task1 ~ s0,s2."+
			"			s2: task1 ~ s0; task0 ~ s1."+
			"		</taskFSM>"+
			"	</tasks>"+
			"	<rules>"+
			"		<rule id=\"rule0\" name=\"rule0\" type=\"location\" parameters=\"time:10.00pm\" />"+
			"		<rule id=\"rule1\" name=\"rule1\" type=\"duration\" parameters=\"location:1,2,3\" />"+
			"		<ruleSet id=\"ruleset0\" name=\"ruleset0\" ruleIds=\"rule0\" expression=\"rule0 &amp;&amp; rule1\" />"+
			"	</rules>"+
			"	<updates>"+
			"		<update id=\"u0\" name=\"set0 to true\" propertyId=\"property0\" type=\"set\" parameter=\"true\" />"+
			"	</updates>"+
			"	<pages>"+
			"		<page id=\"page0\" name=\"page0\" layout=\"0\" audio=\"am0\" feedback=\"hm0\">"+
			"			<pageContent layoutElement=\"textelement\" mediaId=\"tm0\" />"+
			"		</page>"+
			"		<mediaScript id=\"script0\" name=\"script0\" stateCount=\"4\" initialState=\"s0\" acceptingStates=\"s2\">"+
			"			s0 page0: button0 ~ -, s1; button1 ~ -, s3."+
			"			s1 page1: button0 ~ -, s2."+
			"			s2 page0: button1 ~ -, END; button0 ~ -, s3."+
			"			s3 page1: button0 ~ -, s1."+
			"		</mediaScript>"+
			"		<mediaScript id=\"script1\" name=\"script1\" stateCount=\"4\" initialState=\"s3\" acceptingStates=\"s1\">"+
			"			s0 page1: button0 ~ -, s1."+
			"			s1 page0: button1 ~ -, END; button0 ~ -, s2."+
			"			s2 page1: button0 ~ -, s0."+
			"			s3 page0: button0 ~ -, s0; button1 ~ -, s2."+
			"		</mediaScript>"+
			"	</pages>"+
			"	<media>"+
			"		<chunkMedia id=\"tm0\" tag=\"tm0\" name=\"tm0\" type=\"text\" />"+
			"		<chunkMedia id=\"hm0\" tag=\"hm0\" name=\"hm0\" type=\"haptic\" />"+
			"		<fileMedia id=\"am0\" name=\"am0\" type=\"audio\" fileName=\"am0\" extension=\"mp3\" />"+
			"		<fileMedia id=\"im0\" name=\"im0\" type=\"image\" fileName=\"im0\" extension=\"jpg\" />"+
			"		<fileMedia id=\"vm0\" name=\"vm0\" type=\"video\" fileName=\"vm0\" extension=\"mp4\" />"+
			"	</media>"+
			"</game>";
	
	static final String CHUNKXML = 
			"<!DOCTYPE chunks SYSTEM \"http://www.ualrcdg.com/chunks.dtd\">"+
			"<chunks>"+
			"	<textChunk id=\"tm0\"> Test text tm0.. </textChunk>"+
			"	<hapticChunk id=\"hm0\"> Test haptic hm0.. </hapticChunk>"+
			"</chunks>";
	
	//TODO
	public static Game parseTheGame( ) throws CDGException
	{
		Document gameDoc = null;
		
		try
		{
			gameDoc = parseDocumentFromStream( new ByteArrayInputStream( GAMEXML.getBytes() ) );
		}
		catch( SAXException e )
		{
			throw new CDGParseException( "Unable to parse " + "game.xml", e );
		}
		 
		catch( IOException e )
		{
			throw new CDGParseException( "Unable to parse " + "game.xml", e );
		}

		NodeList gameNodes = gameDoc.getElementsByTagName( "game" );

		if( gameNodes.getLength( ) == 0 )
		{
			throw new CDGParseException( "No <game> node found." );
		}
		else if( gameNodes.getLength( ) > 1 )
		{
			throw new CDGParseException( "Too many <game> nodes found." );
		}

		Game game = parseGame( gameNodes.item( 0 ) );
				
		return game;
	}
	
	/**
	 * Parses and validates a game whose files are located at a specified path.
	 * 
	 * @param rootPath the path at which the to-be-parsed game's files are located
	 * @return the valid parsed game
	 * @throws CDGException If the game could not be parsed or if the game cannot be determined to be valid
	 */
	public static Game parseGame( Path rootPath ) throws CDGException
	{
		Document gameDoc = null;
		Document chunkDoc = null;
		
		try
		{
			gameDoc = parseDocumentFromFile( rootPath.resolve( "game.xml" ).toFile( ) );
		}
		catch( SAXException | IOException e )
		{
			throw new CDGParseException( "Unable to parse " + "game.xml", e );
		}

		NodeList gameNodes = gameDoc.getElementsByTagName( "game" );

		if( gameNodes.getLength( ) == 0 )
		{
			throw new CDGParseException( "No <game> node found." );
		}
		else if( gameNodes.getLength( ) > 1 )
		{
			throw new CDGParseException( "Too many <game> nodes found." );
		}

		Game game = parseGame( gameNodes.item( 0 ) );
				
		try
		{
			chunkDoc = parseDocumentFromFile( rootPath.resolve( "chunk.xml" ).toFile( ) );
		}
		catch( SAXException | IOException e )
		{
			throw new CDGParseException( "Unable to parse " + "chunk.xml", e );
		}
		
		/*
		 * At this point, it is assumed that the Game Descriptor file has a valid file structure (and that all intra-file references are valid, i.e., that only existing nodes are referenced)
		 * Assuming that the SAXParser has validated the XML file against the DTD, the validity checks that remain will be done in checkGame.
		 */
		if( checkGame( rootPath, game, chunkDoc ) )
		{
			return game;
		}
		
		return null;
	}

	/**
	 * Validates a CDG Game and associated files against the CDG XML specifications 
	 * 
	 * @param rootPath the Path in which the games files to be validated are located 
	 * @param game the Game parsed from the CDG Game's game XML descriptor file 
	 * @param chunkMedia the DOM object containing the chunk media of the given game
	 * @return true if the game is valid, false otherwise
	 * 
	 * @see ChunkMedia
	 */
	private static boolean checkGame( Path rootPath, Game game, Document chunkMedia )
	{
		/* LATER game validation measures:
		 *	FSM descriptions,
		 *	any property-update expressions (update and condition)
		 *	ruleset expressions
		 *	Layout IDs
		 *	rules and rule parameters
		 *	media files (ensure they are valid audio/video/image files)
		 *	all IDs referenced anywhere...
		 *	check that mediaScripts all have all correct transitions for each page type
		 */

		return true;
	}
	
	/**
	 * Parses a Game DTO object (as described in the CDG XML specifications)
	 *
	 * @param gameNode the XML node containing the child nodes and attributes describing a Game object
	 * @return the parsed Game object
	 * @throws CDGException If an error occurs while parsing the Game
	 *
	 * @see Game
	 */
	private static Game parseGame( Node gameNode ) throws CDGException
	{
		NodeList gameChildNodes = gameNode.getChildNodes( );
		int gameChildCount = gameChildNodes.getLength( );

		Character character = null;
		ArrayList< Task > tasks = new ArrayList< Task >( );
		ArrayList< TaskFSM > taskMaps = new ArrayList< TaskFSM >( );
		ArrayList< Rule > rules = new ArrayList< Rule >( );
		ArrayList< RuleSet > ruleSets = new ArrayList< RuleSet >( );
		ArrayList< Page > pages = new ArrayList< Page >( );
		ArrayList< MediaScript > mediaScripts = new ArrayList< MediaScript >( );
		ArrayList< PropertyUpdate > updates = new ArrayList< PropertyUpdate >( );
		ArrayList< PropertyUpdateSet > updateSets = new ArrayList< PropertyUpdateSet >( );
		ArrayList< FileMedia > fileMedia = new ArrayList< FileMedia >( );
		ArrayList< ChunkMedia > chunkMedia = new ArrayList< ChunkMedia >( );

		boolean parsedCharacterNode = false;
		boolean parsedTasksNode = false;
		boolean parsedRulesNode = false;
		boolean parsedPagesNode = false;
		boolean parsedUpdatesNode = false;
		boolean parsedMediaNode = false;
		
		for( int i = 0; i < gameChildCount; i++ )
		{
			Node childNode = gameChildNodes.item( i );
			switch( childNode.getNodeName( ) )
			{
				case "character":
				{
					if( parsedCharacterNode)
					{
						throw new CDGParseException( "<game> node has more than one <character> nodes." );
					}
					character = parseCharacter( childNode );
					parsedCharacterNode = true;
				}
					break;
				case "tasks":
				{
					if( parsedTasksNode )
					{
						throw new CDGParseException( "<game> node has more than one <tasks> nodes." );
					}
					NodeList taskNodes = childNode.getChildNodes( );
					int taskCount = taskNodes.getLength( );
					for( int j = 0; j < taskCount; j++ )
					{
						Node taskNode = taskNodes.item( j );
						switch( taskNode.getNodeName( ) )
						{
							case "task":
								tasks.add( parseTask( taskNode ) );
								break;
							case "taskMap":
								taskMaps.add( parseTaskMap( taskNode ) );
								break;
						}
					}
					if( tasks.size( ) == 0 )
					{
						throw new CDGParseException( "<tasks> node has no <task> nodes." );
					}
					parsedTasksNode = true;
				}
					break;
				case "rules":
				{
					if( parsedRulesNode )
					{
						throw new CDGParseException( "<game> node has more than one <rules> nodes." );
					}
					NodeList ruleNodes = childNode.getChildNodes( );
					int ruleCount = ruleNodes.getLength( );
					for( int j = 0; j < ruleCount; j++ )
					{
						Node ruleNode = ruleNodes.item( j );
						switch( ruleNode.getNodeName( ) )
						{
							case "rule":
								rules.add( parseRule( ruleNode ) );
								break;
							case "ruleSet":
								ruleSets.add( parseRuleSet( ruleNode ) );
								break;
						}
					}
					if( rules.size( ) == 0 )
					{
						throw new CDGParseException( "<rules> node has no <rule> nodes." );
					}
					parsedRulesNode = true;
				}
					break;
				case "pages":
				{
					if( parsedPagesNode )
					{
						throw new CDGParseException( "<game> node has more than one <pages> nodes." );
					}
					NodeList pageNodes = childNode.getChildNodes( );
					int pageCount = pageNodes.getLength( );
					for( int j = 0; j < pageCount; j++ )
					{
						Node pageNode = pageNodes.item( j );
						switch( pageNode.getNodeName( ) )
						{
							case "page":
								pages.add( parsePage( pageNode ) );
								break;
							case "mediaScript":
								mediaScripts.add( parseMediaScript( pageNode ) );
								break;
						}
					}
					if( pages.size( ) == 0 )
					{
						throw new CDGParseException( "<pages> node has no <page> nodes." );
					}
					if( mediaScripts.size( ) == 0 )
					{
						throw new CDGParseException( "<pages> node has no <mediaScript> nodes." );
					}
					parsedPagesNode = true;
				}
					break;
				case "updates":
				{
					if( parsedUpdatesNode )
					{
						throw new CDGParseException( "<game> node has more than one <updates> nodes." );
					}
					NodeList updateNodes = childNode.getChildNodes( );
					int updateCount = updateNodes.getLength( );
					for( int j = 0; j < updateCount; j++ )
					{
						Node updateNode = updateNodes.item( j );
						switch( updateNode.getNodeName( ) )
						{
							case "update":
								updates.add( parsePropertyUpdate( updateNode ) );
								break;
							case "updateSet":
								updateSets.add( parsePropertyUpdateSet( updateNode ) );
								break;
						}
					}
					parsedUpdatesNode = true;
				}
					break;
				case "media":
				{
					if( parsedMediaNode )
					{
						throw new CDGParseException( "<game> node has more than one <media> nodes." );
					}
					NodeList mediaNodes = childNode.getChildNodes( );
					int mediaCount = mediaNodes.getLength( );
					for( int j = 0; j < mediaCount; j++ )
					{
						Node mediaNode = mediaNodes.item( j );
						switch( mediaNode.getNodeName( ) )
						{
							case "fileMedia":
								fileMedia.add( parseFileMedia( mediaNode ) );
								break;
							case "chunkMedia":
								chunkMedia.add( parseChunkMedia( mediaNode ) );
								break;
						}
					}
					parsedMediaNode = true;
				}
					break;
				case "#text":
					//do nothing; ignore unexpected text nodes (comments / spacing)
					break;
				default:
					throw new CDGParseException( "<game> node has unexpected child node: <" + childNode.getNodeName( ) + ">" );
			}
		}

		if( parsedCharacterNode == false )
		{
			throw new CDGParseException( "<game> node missing child node: <character>" );
		}
		if( parsedTasksNode == false )
		{
			throw new CDGParseException( "<game> node missing child node: <tasks>" );
		}
		if( parsedRulesNode == false )
		{
			throw new CDGParseException( "<game> node missing child node: <rules>" );
		}
		if( parsedPagesNode == false )
		{
			throw new CDGParseException( "<game> node missing child node: <pages>" );
		}
		if( parsedUpdatesNode == false )
		{
			throw new CDGParseException( "<game> node missing child node: <updates>" );
		}
		if( parsedMediaNode == false )
		{
			throw new CDGParseException( "<game> node missing child node: <media>" );
		}
		
		return new Game( getProperty( gameNode, "name", true ), getProperty( gameNode, "description", true ), 
				getProperty( gameNode, "rootTask", true ), character, 
				tasks.toArray( new Task[0] ), taskMaps.toArray( new TaskFSM[0] ), 
				rules.toArray( new Rule[0] ), ruleSets.toArray( new RuleSet[0] ), 
				updates.toArray( new PropertyUpdate[0] ), updateSets.toArray( new PropertyUpdateSet[0] ), pages.toArray( new Page[0] ), mediaScripts.toArray( new MediaScript[0] ),
				fileMedia.toArray( new FileMedia[0] ), chunkMedia.toArray( new ChunkMedia[0] ) );
	}

	/**
	 * Convenience method for retrieving the value of a particular XML node's specified property
	 * 
	 * @param node the XML node from which the property value should be retrieved
	 * @param propertyName the name of the property being retrieved
	 * @param throwOnNotFound true if this method should throw an exception if the property's value cannot be retrieved, false otherwise
	 * @return the value of the desired property
	 * @throws CDGParseException If throwOnNotFound is true, thrown if the property's value cannot be retrieved
	 */
	public static String getProperty( Node node, String propertyName, boolean throwOnNotFound ) throws CDGParseException
	{
		NamedNodeMap attributes = node.getAttributes( );
		if( attributes == null )
		{
			if( throwOnNotFound )
			{
				throw new CDGParseException( "Cannot get properties from Node <" + node.getNodeName( ) + ">" );
			}
			else
			{
				return null;
			}
		}
		Node attribute = attributes.getNamedItem( propertyName );
		if( attribute == null )
		{
			if( throwOnNotFound )
			{
				throw new CDGParseException( "Cannot get Property " + propertyName + " from Node <" + node.getNodeName( ) + ">" );
			}
			else
			{
				return null;
			}
		}
		return attribute.getNodeValue( );
	}
	
	/**
	 * Parses a Character DTO object (as described in the CDG XML specifications)
	 *
	 * @param characterNode the XML node containing the child nodes and attributes describing a Character object
	 * @return the parsed Character object
	 * @throws CDGException If an error occurs while parsing the Character
	 *
	 * @see Character
	 */
	private static Character parseCharacter( Node characterNode ) throws CDGException
	{
		NodeList childNodes = characterNode.getChildNodes( );
		int childCount = childNodes.getLength( );

		ArrayList< SimpleProperty< ? > > properties = new ArrayList< SimpleProperty<?> >( );
		
		for( int i = 0; i < childCount; i++ )
		{
			Node characterChild = childNodes.item( i );
			switch( characterChild.getNodeName( ) )
			{
				case "properties":
				{
					NodeList propertyNodes = characterChild.getChildNodes( );
					int propertyCount = propertyNodes.getLength( );
					for( int j = 0; j < propertyCount; j++ )
					{
						Node propertyChild = propertyNodes.item( j );
						switch( propertyChild.getNodeName( ) )
						{
							case "simpleProperty":
								properties.add( parseProperty( propertyChild ) );
								break;
							case "#text":
								break;
							default:
								throw new CDGParseException( "<properties> node has unexpected childe node: <" + propertyChild.getNodeName( ) + ">" ); 
						}
					}
				}
					break;
				case "#text":
					break;
				default:
					throw new CDGParseException( "<character> node has unexpected childe node: <" + characterChild.getNodeName( ) + ">" );		
			}
		}
		
		return new Character( getProperty( characterNode, "id", true ), properties );
	}

	/**
	 * Parses a SimpleProperty DTO object (as described in the CDG XML specifications)
	 *
	 * @param propertyNode the XML node containing the child nodes and attributes describing a SimpleProperty object
	 * @return the parsed SimpleProperty object
	 * @throws CDGException If an error occurs while parsing the SimpleProperty
	 *
	 * @see SimpleProperty
	 */
	private static SimpleProperty< ? > parseProperty( Node propertyNode ) throws CDGException
	{
		String id = getProperty( propertyNode, "id", true );
		String propertyType = getProperty( propertyNode, "type", true );
		String name = getProperty( propertyNode, "name", true );
		String initialValue = getProperty( propertyNode, "initialValue", true );
		try
		{
			switch( propertyType )
			{
				case "Boolean":
					return new SimpleProperty<>( id, name, Boolean.parseBoolean( initialValue ) );
				case "Character":
					return new SimpleProperty<>( id, name, initialValue.charAt( 0 ) );
				case "Byte":
					return new SimpleProperty<>( id, name, Byte.parseByte( initialValue ) ); 
				case "Short":
					return new SimpleProperty<>( id, name, Short.parseShort( initialValue ) );
				case "Integer":
					return new SimpleProperty<>( id, name, Integer.parseInt( initialValue ) );
				case "Long":
					return new SimpleProperty<>( id, name, Long.parseLong( initialValue ) );
				case "BigInteger":
					return new SimpleProperty<>( id, name, new BigInteger( initialValue ) );
				case "Float":
					return new SimpleProperty<>( id, name, Float.parseFloat( initialValue ) );
				case "Double":
					return new SimpleProperty<>( id, name, Double.parseDouble( initialValue ) );
				case "BigDecimal":
					return new SimpleProperty<>( id, name, new BigDecimal( Double.parseDouble( initialValue ) ) );
				case "String":
					return new SimpleProperty<>( id, name, initialValue );
				default:
					throw new CDGParseException( "Unable to create SimpleProperty [id=" + id + ", name=" + name + ", propertyType=" + propertyType+ ", value=" + initialValue + "]" );
			}
		}
		catch( Exception e )
		{
			throw new CDGException( "Could not parse <property> node: SimpleProperty [id=" + id + ", name=" + name + ", initialValue=" + initialValue + "]", e );
		}
	}

	/**
	 * Parses a Task DTO object (as described in the CDG XML specifications)
	 *
	 * @param taskNode the XML node containing the child nodes and attributes describing a Task object
	 * @return the parsed Task object
	 * @throws CDGException If an error occurs while parsing the Task
	 *
	 * @see Task
	 */
	private static Task parseTask( Node taskNode ) throws CDGParseException
	{
		return new Task( getProperty( taskNode, "id", true ), getProperty( taskNode, "name", true ), getProperty( taskNode, "description", true ), getProperty( taskNode, "preMediaScriptId", true ), getProperty( taskNode, "ruleId", true ), getProperty( taskNode, "postMediaScriptId", true ) );
	}
	
	//LATER consider changing FSM descriptions to allow for simpler parsing - e.g. transition table a la Minsker
	/**
	 * Parses a TaskFSM DTO object (as described in the CDG XML specifications)
	 *
	 * @param taskMapNode the XML node containing the child nodes and attributes describing a TaskFSM object
	 * @return the parsed TaskFSM object
	 * @throws CDGException If an error occurs while parsing the TaskFSM
	 *
	 * @see TaskFSM
	 */
	private static TaskFSM parseTaskMap( Node taskMapNode ) throws CDGException, CDGException
	{
		String[] acceptingStatesString = getProperty( taskMapNode, "acceptingStates", true ).split( whiteSpacePattern );
		int[] acceptingStates = new int[acceptingStatesString.length];
		for( int i = 0; i < acceptingStatesString.length; i++ )
		{
			acceptingStates[i] = Integer.parseInt( acceptingStatesString[i] );
		}
		
		int stateCount = Integer.parseInt( getProperty( taskMapNode, "stateCount", true ) );
		
		HashMap< Integer, HashMap< Integer, ArrayList< String > > > transitionMap = new HashMap< Integer, HashMap< Integer, ArrayList< String > > >( );
		
		String[] lines = taskMapNode.getNodeValue( ).split( stateSplitPattern );
		
		if( lines.length != stateCount )
		{
			throw new CDGParseException( "Could not parse <taskFSM> node: number of state lines (" + lines.length + ") does not match stateCount (" + stateCount + ")." );
		}
		
		for( int stateNumber = 0; stateNumber < lines.length; stateNumber++ )
		{
			String[] line = lines[stateNumber].split( "\\s*" + ":" + "\\s*" ); //a given state's transition row [name, transitions]
			if( !line[0].equals( "s" + stateNumber ) )
			{
				throw new CDGParseException( "Could not parse <taskFSM> node: state node " + stateNumber + " had label " + line[0] + ". Expected s" + stateNumber );
			}
			
			HashMap< Integer, ArrayList< String > > rowMap = new HashMap< Integer, ArrayList<String> >( );
			
			switch( line.length )
			{
				case 1:
					//Assume that this is an accepting state, which needs no transitions
					break;
				case 2:
				{
					String[] transitions = line[1].split( "\\s*" + ";" + "\\s*" ); // a given state's transition list [transition0, transition1, ...]
					for( int transitionNumber = 0; transitionNumber < transitions.length; transitionNumber++ )
					{
						String[] transition = transitions[transitionNumber].split( "\\s*" + "~" + "\\s*" ); // a given transition [transition, destinations]
						if( transition.length != 2 )
						{
							throw new CDGParseException( "Could not parse <taskFSM> node: s" + stateNumber + "'s " + transitionNumber + "th transition is formatted incorrectly." );
						}
						
						String ruleName = transition[0];
						String[] destinations = transition[1].split( "\\s*" + "," + "\\s*" ); // a given transition's destinations [destination0, destination1, ...]
						for( String d : destinations )
						{
							int destination = Integer.parseInt( d.substring( 1 ) );
							
							ArrayList< String > transitionList = rowMap.get( destination );
							if( transitionList == null )
							{
								transitionList = new ArrayList< String >();
								rowMap.put( destination, transitionList );
							}
							
							if( destination < 0 || destination >= stateCount )
							{
								throw new CDGParseException( "Could not parse <taskFSM> node: s" + stateNumber + "'s " + transitionNumber + "th transition leads to an invalid state: " + d + "." );
							}
							
							transitionList.add( ruleName );
						}
					}
				}
				break;
				default:
					throw new CDGParseException( "Could not parse <taskFSM> node: state node " + stateNumber + " is formatted incorrectly." );	
			}
		
			transitionMap.put( stateNumber, rowMap );
		}
		
		String[][][] transitionMatrix = new String[stateCount][stateCount][]; // [originState][destinationState][transition]
		for( int o = 0; o < stateCount; o++ )
		{
			HashMap< Integer, ArrayList< String > > row = transitionMap.get( o );
			for( int d = 0; d < stateCount; d++ )
			{
				ArrayList< String > transitions = row.get( d );
				if( transitions == null )
				{
					transitionMatrix[o][d] = new String[0];
				}
				else
				{
					transitionMatrix[o][d] = row.get(d).toArray( new String[0] );
				}
			}
		}
		
		return new TaskFSM( getProperty( taskMapNode, "id", true ), getProperty( taskMapNode, "name", true ), stateCount,Integer.parseInt( getProperty( taskMapNode, "initialState", true ) ), acceptingStates, transitionMatrix );
	}
	
	/**
	 * Parses a Rule DTO object (as described in the CDG XML specifications)
	 *
	 * @param ruleNode the XML node containing the child nodes and attributes describing a Rule object
	 * @return the parsed Rule object
	 * @throws CDGException If an error occurs while parsing the Rule
	 *
	 * @see Rule
	 */
	private static Rule parseRule( Node ruleNode ) throws CDGParseException
	{
		String ruleId = getProperty( ruleNode, "id", true );
		String[] parameterArray = getProperty( ruleNode, "parameters", true ).split( "\\s*" + ";" + "\\s*" );
		if( parameterArray.length == 0 )
		{
			throw new CDGParseException( "Rule " + ruleId + " must have at least one parameter." );
		}
		
		HashMap< String, String > parameters = new HashMap< String, String >( );
		for( String p : parameterArray )
		{
			String[] parameter = p.split( "\\s*" + ":" + "\\s*" );
			if( parameter.length != 2 )
			{
				throw new CDGParseException( "Rule parameters for rule " + ruleId + " must be in the form (name):(value)" ); 
			}
			parameters.put( parameter[0], parameter[1] );
		}
		return new Rule( ruleId, getProperty( ruleNode, "name", true ), getProperty( ruleNode, "type", true ), parameters );
	}
	
	/**
	 * Parses a RuleSet DTO object (as described in the CDG XML specifications)
	 *
	 * @param ruleSetNode the XML node containing the child nodes and attributes describing a RuleSet object
	 * @return the parsed RuleSet object
	 * @throws CDGException If an error occurs while parsing the RuleSet
	 *
	 * @see RuleSet
	 */
	private static RuleSet parseRuleSet( Node ruleSetNode ) throws CDGParseException
	{
		return new RuleSet( getProperty( ruleSetNode, "id", true ), getProperty( ruleSetNode, "name", true ), getProperty( ruleSetNode, "ruleIds", true ).split( whiteSpacePattern ), getProperty( ruleSetNode, "expression", true ) );
	}

	/**
	 * Parses a Page DTO object (as described in the CDG XML specifications)
	 *
	 * @param pageNode the XML node containing the child nodes and attributes describing a Page object
	 * @return the parsed Page object
	 * @throws CDGException If an error occurs while parsing the Page
	 *
	 * @see Page
	 */
	private static Page parsePage( Node pageNode ) throws CDGParseException
	{
		HashMap< String, String> contents = new HashMap< String, String >( );
		NodeList childNodes = pageNode.getChildNodes( );
		int nodeCount = childNodes.getLength( );
		for( int j = 0; j < nodeCount; j++ )
		{
			Node childNode = childNodes.item( j );
			switch( childNode.getNodeName( ) )
			{
				case "pageContent":
					contents.put( getProperty( childNode, "layoutElement", true ), getProperty( childNode, "mediaId", true ) );
					break;
			}
		}
		
		String pageId = getProperty( pageNode, "id", true );
		if( contents.keySet( ).size( ) == 0 )
		{
			throw new CDGParseException( "Page (" + pageId + ") must have at least one pageContent." );
		}
		
		String entranceUpdatesRaw = getProperty( pageNode, "entranceUpdates", false );
		String[] entranceUpdates = null;
		if( entranceUpdatesRaw != null )
		{
			entranceUpdates = entranceUpdatesRaw.split( whiteSpacePattern );
		}
		
		String exitUpdatesRaw = getProperty( pageNode, "exitUpdates", false );
		String[] exitUpdates = null;
		if( exitUpdatesRaw != null )
		{
			exitUpdates = exitUpdatesRaw.split( whiteSpacePattern );
		}
		
		Page p = new Page( pageId, getProperty( pageNode, "name", true ), getProperty( pageNode, "layout", true ), getProperty( pageNode, "audio", true ).split( whiteSpacePattern ), getProperty( pageNode, "feedback", true ).split( whiteSpacePattern ), entranceUpdates, exitUpdates, contents );
		
		return p;
	}
	
	/**
	 * Parses a PropertyUpdate DTO object (as described in the CDG XML specifications)
	 *
	 * @param updateNode the XML node containing the child nodes and attributes describing a PropertyUpdate object
	 * @return the parsed PropertyUpdate object
	 * @throws CDGException If an error occurs while parsing the PropertyUpdate
	 *
	 * @see PropertyUpdate
	 */
	private static PropertyUpdate parsePropertyUpdate( Node updateNode ) throws CDGParseException 
	{
		return new PropertyUpdate( getProperty( updateNode, "id", true ), getProperty( updateNode, "name", true ), getProperty( updateNode, "propertyId", true ), getProperty( updateNode, "type", true ), getProperty( updateNode, "parameter", true ) );
	}
	
	/**
	 * Parses a PropertyUpdateSet DTO object (as described in the CDG XML specifications)
	 *
	 * @param updateNode the XML node containing the child nodes and attributes describing a PropertyUpdateSet object
	 * @return the parsed PropertyUpdateSet object
	 * @throws CDGException If an error occurs while parsing the PropertyUpdateSet
	 *
	 * @see PropertyUpdateSet
	 */
	private static PropertyUpdateSet parsePropertyUpdateSet( Node updateNode ) throws CDGParseException 
	{
		return new PropertyUpdateSet( getProperty( updateNode, "id", true ), getProperty( updateNode, "updateIds", true ).split( whiteSpacePattern ) );
	}
	
	/**
	 * Parses a MediaScript DTO object (as described in the CDG XML specifications)
	 * 
	 * @param mediaScriptNode the XML node containing the child nodes and attributes describing a MediaScript object
	 * @return the parsed MediaScript object
	 * @throws CDGException If an error occurs while parsing the MediaScript
	 * 
	 * @see MediaScript
	 */
	private static MediaScript parseMediaScript( Node mediaScriptNode ) throws CDGException
	{
		String[] acceptingStatesString = getProperty( mediaScriptNode, "acceptingStates", true ).split( whiteSpacePattern );
		int[] acceptingStates = new int[acceptingStatesString.length];
		for( int i = 0; i < acceptingStatesString.length; i++ )
		{
			acceptingStates[i] = Integer.parseInt( acceptingStatesString[i].substring( 1 ) );
		}
		
		int stateCount = Integer.parseInt( getProperty( mediaScriptNode, "stateCount", true ) );
		
		HashMap< String, HashMap< String, MediaScriptEntry > > transitionMap = new HashMap< String, HashMap< String, MediaScriptEntry > >( );
		HashMap< String, String > pageList = new HashMap< String, String >( );
		
		NodeList childNodes = mediaScriptNode.getChildNodes( );
		int childNodesCount = childNodes.getLength( );
		
		if( childNodesCount != 1 )
		{
			throw new CDGParseException( "Could not parse <mediaScript> node: too many childe nodes ( count = " + childNodesCount + ")." );
		}
		
		String[] lines = childNodes.item( 0 ).getNodeValue( ).trim( ).split( stateSplitPattern );
		
		if( lines.length != stateCount )
		{
			throw new CDGParseException( "Could not parse <mediaScript> node: number of state lines (" + lines.length + ") does not match stateCount (" + stateCount + ")." );
		}
		
		for( int stateNumber = 0; stateNumber < lines.length; stateNumber++ )
		{
			String originStateName = "s" + stateNumber;
			
			String[] line = lines[stateNumber].split( "\\s*" + ":" + "\\s*" ); //a given state's transition row [name, transitions]
			String[] state = line[0].split( whiteSpacePattern ); //a given state's header info [stateName, pageName]
			if( state.length != 2 )
			{
				throw new CDGParseException( "Could not parse <mediaScript> node: state node " + stateNumber + "'s header information is formatted incorrectly." );
			}
			if( !state[0].equals( originStateName ) )
			{
				throw new CDGParseException( "Could not parse <mediaScript> node: state node " + stateNumber + " had label " + state[0] + ". Expected s" + stateNumber );
			}
			
			pageList.put( originStateName, state[1] );
			
			HashMap< String, MediaScriptEntry > rowMap = new HashMap< String, MediaScriptEntry >( );
			
			switch( line.length )
			{
				case 2:
				{
					String[] transitions = line[1].split( "\\s*" + ";" + "\\s*" ); // a given state's transition list [transition0, transition1, ...]
					for( int transitionNumber = 0; transitionNumber < transitions.length; transitionNumber++ )
					{
						String[] transition = transitions[transitionNumber].split( "\\s*" + "~" + "\\s*" ); // a given transition [transition, destination details]
						if( transition.length != 2 )
						{
							throw new CDGParseException( "Could not parse <mediaScript> node: s" + stateNumber + "'s " + transitionNumber + "th transition is formatted incorrectly." );
						}
						
						String buttonName = transition[0];
						String[] destinationInformation = transition[1].split( "\\s*" + "," + "\\s*" ); // a given transition's destination information [update, destination]
						
						if( destinationInformation.length != 2 )
						{
							throw new CDGParseException( "Could not parse <mediaScript> node: s" + stateNumber + "'s " + transitionNumber + "th transition is formatted incorrectly." );
						}
						
						String updateId = destinationInformation[0];
						if( updateId.equals( "-" ) )
						{
							updateId = MediaScript.NULL_UPDATE;
						}
						
						String destinationState = destinationInformation[1];
						
						if( destinationState.equals( "END" ) )
						{
							destinationState = MediaScript.END_TRANISITION;
						}
						else if( destinationState.equals( "-" ) )
						{
							destinationState = MediaScript.NULL_TRANISITION;
						}
						else
						{
							int destination = Integer.parseInt( destinationState.substring( 1 ) );
							
							if( destination < 0 || destination >= stateCount )
							{
								throw new CDGParseException( "Could not parse <mediaScript> node: s" + stateNumber + "'s " + transitionNumber + "th transition leads to an invalid state: " + destinationState + "." );
							}
						}
						
						if( rowMap.containsKey( buttonName ) )
						{
							throw new CDGParseException( "Could not parse <mediaScript> node: s" + stateNumber + " has multiple transitions for " + buttonName + "." );
						}
						
						rowMap.put( buttonName, new MediaScriptEntry( updateId, destinationState ) );
					}
				}
				break;
				default:
					throw new CDGParseException( "Could not parse <mediaScript> node: state node " + stateNumber + " is formatted incorrectly." );	
			}
		
			transitionMap.put( originStateName, rowMap );
		}
		
		return new MediaScript( getProperty( mediaScriptNode, "id", true ), getProperty( mediaScriptNode, "name", true ), getProperty( mediaScriptNode, "initialState", true ), getProperty( mediaScriptNode, "acceptingStates", true ).split( whiteSpacePattern ), pageList, transitionMap );
	}

	/**
	 * Parses a ChunkMedia DTO object (as described in the CDG XML specifications)
	 *
	 * @param chunkMediaNode the XML node containing the child nodes and attributes describing a ChunkMedia object
	 * @return the parsed ChunkMedia object
	 * @throws CDGException If an error occurs while parsing the ChunkMedia
	 *
	 * @see ChunkMedia
	 */
	private static ChunkMedia parseChunkMedia( Node chunkMediaNode ) throws CDGTypeException, CDGParseException
	{
		return new ChunkMedia( getProperty( chunkMediaNode, "id", true ), getProperty( chunkMediaNode, "name", true ), getProperty( chunkMediaNode, "type", true ), getProperty( chunkMediaNode, "tag", true ) );
	}
	
	/**
	 * Parses a FileMedia DTO object (as described in the CDG XML specifications)
	 *
	 * @param fileMediaNode the XML node containing the child nodes and attributes describing a FileMedia object
	 * @return the parsed FileMedia object
	 * @throws CDGException If an error occurs while parsing the FileMedia
	 *
	 * @see FileMedia
	 */
	private static FileMedia parseFileMedia( Node fileMediaNode ) throws CDGTypeException, CDGParseException
	{
		return new FileMedia( getProperty( fileMediaNode, "id", true ), getProperty( fileMediaNode, "name", true ), getProperty( fileMediaNode, "type", true ), getProperty( fileMediaNode, "fileName", true ) + "." + getProperty( fileMediaNode, "extension", true )  );
	}
	
	/**
	 * Encodes a CDG Game media file (with Base-64 encoding) and wraps it in an XML document structure 
	 * 
	 * @param input the file to be encoded
	 * @param fileName the name of the file to be encoded
	 * @param output the file to which the file's encoded contents should be written
	 * @throws FileNotFoundException If either of the specified files cannot be located
	 * @throws IOException If the input file cannot be read from or the output file cannot be written to
	 */
	public static void encodeAndXMLWrapMediaFile( File input, String fileName, File output ) throws FileNotFoundException, IOException
	{
		System.out.println( "CDGXMLHelper: encoding " + input.getCanonicalPath( ) + " to " + output.getCanonicalPath( ) );
		
		BufferedInputStream is = new BufferedInputStream( new FileInputStream( input ) );
		FileOutputStream fos =  new FileOutputStream( output, false );
		Base64OutputStream bos = new Base64OutputStream( fos );
		BufferedOutputStream os = new BufferedOutputStream( bos );
		
		//LATER make this dynamic, check flushes 
		fos.write( ( "<!DOCTYPE media SYSTEM \"http://www.ualrcdg.com/media.dtd\">" + newline ).getBytes( ) );
		fos.write( ( "<media fileName=\"" + fileName + "\">" + newline ).getBytes( ) );
		fos.flush( );
		
		copyStream( is, os );
		
		os.flush( );
		bos.flush( );
		fos.flush( );
		os.close( );
		
		//LATER fix Base64 flushing problem... Shouldn't need to reopen fos here
		fos = new FileOutputStream( output, true );
		fos.write( (newline + "</media>").getBytes( ) );
		fos.flush( );
		fos.close( );
		
		is.close( );
	}

	//LATER Need decodeXMLWrappedMediaFile method
	
	/*************************************************************************************************************************************************************************************************
	 ************************************************************************************************************************************************************************************************* 
	 *************************************************************************************************************************************************************************************************/

	/**
	 * Creates an RegisterPlayer request document from an {@link RegisterPlayerRequest} object
	 *
	 * @param request the RegisterPlayerRequest object to be used in generating the XML request document
	 * @return the generated RegisterPlayer request XML document
	 */
	public static Document createRegisterPlayerRequestDocument( RegisterPlayerRequest request )
	{
		Document requestDoc = builder.newDocument( );
		
		DocumentType docType = requestDoc.getImplementation( ).createDocumentType( RegisterPlayerRequest.requestTag, null, CDGRequest.dtdDomain + RegisterPlayerRequest.dtdName );
		
		Element root = requestDoc.createElement( RegisterPlayerRequest.requestTag );
		
		root.setAttribute( "requestId", request.requestId );
		root.setAttribute( "username", request.username );
		root.setAttribute( "password", request.password );
		
		requestDoc.appendChild( docType );
		requestDoc.appendChild( root );
		
		return requestDoc;
	}
	
	/**
	 * Parses an {@link RegisterPlayerRequest} object from an XML request document
	 * 
	 * @param doc the XML request document containing the RegisterPlayerRequest information
	 * @return the parsed RegisterPlayerRequest object
	 * @throws CDGParseException If the request document cannot be parsed
	 */
	public static RegisterPlayerRequest parseRegisterPlayerRequestDocument( Document doc ) throws CDGParseException
	{
		NodeList requestNodes = doc.getElementsByTagName( RegisterPlayerRequest.requestTag );
		
		if( requestNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseRegisterPlayerRequest: cannot parse request. Incorrect number of <" + RegisterPlayerRequest.requestTag + "> tags found." );
		}
		
		Node requestNode = requestNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( requestNode, "requestId", true );
		String username = CDGXMLHelper.getProperty( requestNode, "username", true );
		String password = CDGXMLHelper.getProperty( requestNode, "password", true );
		return new RegisterPlayerRequest( requestId, username, password );
	}
	
	/**
	 * Creates an RegisterPlayer response document from an {@link RegisterPlayerResponse} object
	 *
	 * @param response the RegisterPlayerResponse object to be used in generating the XML response document
	 * @return the generated RegisterPlayer response XML document
	 */
	public static Document createRegisterPlayerResponseDocument( RegisterPlayerResponse response )
	{
		Document responseDoc = builder.newDocument( );
		
		DocumentType docType = responseDoc.getImplementation( ).createDocumentType( RegisterPlayerResponse.responseTag, null, CDGResponse.dtdDomain + RegisterPlayerResponse.dtdName );
		
		Element root = responseDoc.createElement( RegisterPlayerResponse.responseTag );
		
		root.setAttribute( "requestId", response.requestId );
		root.setAttribute( "response", Boolean.toString( response.response ) );
		
		responseDoc.appendChild( docType );
		responseDoc.appendChild( root );
		
		return responseDoc;
	}
	
	/**
	 * Parses an {@link RegisterPlayerResponse} object from an XML response document
	 * 
	 * @param doc the XML response document containing the RegisterPlayerResponse information
	 * @return the parsed RegisterPlayerResponse object
	 * @throws CDGParseException If the response document cannot be parsed
	 */
	public static RegisterPlayerResponse parseRegisterPlayerResponseDocument( Document doc ) throws CDGParseException
	{
		NodeList responseNodes = doc.getElementsByTagName( RegisterPlayerResponse.responseTag );
		
		if( responseNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseRegisterPlayerResponse: cannot parse request. Incorrect number of <" + RegisterPlayerResponse.responseTag + "> tags found." );
		}
		
		Node responseNode = responseNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( responseNode, "requestId", true );
		boolean response = Boolean.parseBoolean( CDGXMLHelper.getProperty( responseNode, "response", true ) );
		
		return new RegisterPlayerResponse( requestId, response );
	}
		
	/*************************************************************************************************************************************************************************************************
	 ************************************************************************************************************************************************************************************************* 
	 *************************************************************************************************************************************************************************************************/

	/**
	 * Creates an AuthenticatePlayer request document from an {@link AuthenticatePlayerRequest} object
	 *
	 * @param request the AuthenticatePlayerRequest object to be used in generating the XML request document
	 * @return the generated AuthenticatePlayer request XML document
	 */
	public static Document createAuthenticatePlayerRequestDocument( AuthenticatePlayerRequest request )
	{
		Document requestDoc = builder.newDocument( );
		
		DocumentType docType = requestDoc.getImplementation( ).createDocumentType( AuthenticatePlayerRequest.requestTag, null, CDGRequest.dtdDomain + AuthenticatePlayerRequest.dtdName );
		
		Element root = requestDoc.createElement( AuthenticatePlayerRequest.requestTag );
		
		root.setAttribute( "requestId", request.requestId );
		root.setAttribute( "username", request.username );
		root.setAttribute( "password", request.password );
		
		requestDoc.appendChild( docType );
		requestDoc.appendChild( root );
		
		return requestDoc;
	}

	/**
	 * Parses an {@link AuthenticatePlayerRequest} object from an XML request document
	 * 
	 * @param doc the XML request document containing the AuthenticatePlayerRequest information
	 * @return the parsed AuthenticatePlayerRequest object
	 * @throws CDGParseException If the request document cannot be parsed
	 */
	public static AuthenticatePlayerRequest parseAuthenticatePlayerRequestDocument( Document doc ) throws CDGParseException
	{
		NodeList requestNodes = doc.getElementsByTagName( AuthenticatePlayerRequest.requestTag );
		
		if( requestNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseAuthenticatePlayerRequest: cannot parse request. Incorrect number of <" + AuthenticatePlayerRequest.requestTag + "> tags found." );
		}
		
		Node requestNode = requestNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( requestNode, "requestId", true );
		String username = CDGXMLHelper.getProperty( requestNode, "username", true );
		String password = CDGXMLHelper.getProperty( requestNode, "password", true );
		return new AuthenticatePlayerRequest( requestId, username, password );
	}
	
	/**
	 * Creates an AuthenticatePlayer response document from an {@link AuthenticatePlayerResponse} object
	 *
	 * @param response the AuthenticatePlayerResponse object to be used in generating the XML response document
	 * @return the generated AuthenticatePlayer response XML document
	 */
	public static Document createAuthenticatePlayerResponseDocument( AuthenticatePlayerResponse response )
	{
		Document responseDoc = builder.newDocument( );
		
		DocumentType docType = responseDoc.getImplementation( ).createDocumentType( AuthenticatePlayerResponse.responseTag, null, CDGResponse.dtdDomain + AuthenticatePlayerResponse.dtdName );
		
		Element root = responseDoc.createElement( AuthenticatePlayerResponse.responseTag );
		
		root.setAttribute( "requestId", response.requestId );
		root.setAttribute( "response", Boolean.toString( response.response ) );
		
		responseDoc.appendChild( docType );
		responseDoc.appendChild( root );
		
		return responseDoc;
	}
	
	/**
	 * Parses an {@link AuthenticatePlayerResponse} object from an XML response document
	 * 
	 * @param doc the XML response document containing the AuthenticatePlayerResponse information
	 * @return the parsed AuthenticatePlayerResponse object
	 * @throws CDGParseException If the response document cannot be parsed
	 */
	public static AuthenticatePlayerResponse parseAuthenticatePlayerResponseDocument( Document doc ) throws CDGParseException
	{
		NodeList responseNodes = doc.getElementsByTagName( AuthenticatePlayerResponse.responseTag );
		
		if( responseNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseAuthenticatePlayerResponse: cannot parse request. Incorrect number of <" + AuthenticatePlayerResponse.responseTag + "> tags found." );
		}
		
		Node responseNode = responseNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( responseNode, "requestId", true );
		boolean response = Boolean.parseBoolean( CDGXMLHelper.getProperty( responseNode, "response", true ) );
		return new AuthenticatePlayerResponse( requestId, response );
	}
	
	/*************************************************************************************************************************************************************************************************
	 ************************************************************************************************************************************************************************************************* 
	 *************************************************************************************************************************************************************************************************/
		
	/**
	 * Creates an GameList request document from an {@link GameListRequest} object
	 *
	 * @param request the GameListRequest object to be used in generating the XML request document
	 * @return the generated GameList request XML document
	 */
	public static Document createGameListRequestDocument( GameListRequest request )
	{
		Document requestDoc = builder.newDocument( );
		
		DocumentType docType = requestDoc.getImplementation( ).createDocumentType( GameListRequest.requestTag, null, CDGRequest.dtdDomain + GameListRequest.dtdName );
		
		Element root = requestDoc.createElement( GameListRequest.requestTag  );
		
		root.setAttribute( "requestId", request.requestId );
		
		requestDoc.appendChild( docType );
		requestDoc.appendChild( root );
		
		return requestDoc;
	}
	
	/**
	 * Parses an {@link GameListRequest} object from an XML request document
	 * 
	 * @param doc the XML request document containing the GameListRequest information
	 * @return the parsed GameListRequest object
	 * @throws CDGParseException If the request document cannot be parsed
	 */
	public static GameListRequest parseGameListRequestDocument( Document doc ) throws CDGParseException
	{
		NodeList requestNodes = doc.getElementsByTagName( GameListRequest.requestTag );
		
		if( requestNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseGameListRequest: cannot parse request. Incorrect number of <" + GameListRequest.requestTag + "> tags found." );
		}
		
		Node requestNode = requestNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( requestNode, "requestId", true );
		return new GameListRequest( requestId );
	}
	
	/**
	 * Creates an GameList response document from an {@link GameListResponse} object
	 *
	 * @param response the GameListResponse object to be used in generating the XML response document
	 * @return the generated GameList response XML document
	 */
	public static Document createGameListResponseDocument( GameListResponse response )
	{
		Document responseDoc = builder.newDocument( );
		
		DocumentType docType = responseDoc.getImplementation( ).createDocumentType( GameListResponse.responseTag, null, CDGResponse.dtdDomain + GameListResponse.dtdName );
		
		Element root = responseDoc.createElement( GameListResponse.responseTag  );
		
		root.setAttribute( "requestId", response.requestId );
		
		for( GameHeader game : response.list )
		{
			Element gameNode = responseDoc.createElement( "game" );
			gameNode.setAttribute( "id", game.id );
			gameNode.setAttribute( "name", game.name );
			gameNode.setAttribute( "description", game.description );
			root.appendChild( gameNode );
		}
		
		responseDoc.appendChild( docType );
		responseDoc.appendChild( root );
		
		return responseDoc;
	}
	
	/**
	 * Parses an {@link GameListResponse} object from an XML response document
	 * 
	 * @param doc the XML response document containing the GameListResponse information
	 * @return the parsed GameListResponse object
	 * @throws CDGParseException If the response document cannot be parsed
	 */
	public static GameListResponse parseGameListResponseDocument( Document doc ) throws CDGParseException
	{
		NodeList responseNodes = doc.getElementsByTagName( GameListResponse.responseTag );
		
		if( responseNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseGameListResponse: cannot parse request. Incorrect number of <" + GameListResponse.responseTag + "> tags found." );
		}
		
		Node responseNode = responseNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( responseNode, "requestId", true );
		
		ArrayList< GameHeader > games = new ArrayList< GameHeader >( );
		
		NodeList childNodes = responseNode.getChildNodes( );
		
		int childNodesLength = childNodes.getLength( );
		
		for( int i = 0; i < childNodesLength; i++ )
		{
			Node childNode = childNodes.item( i );
			if( childNode.getNodeName( ).equals( "game" ) )
			{
				games.add( new GameHeader( CDGXMLHelper.getProperty( childNode, "id", true ), CDGXMLHelper.getProperty( childNode, "name", true ), CDGXMLHelper.getProperty( childNode, "description", true ) ) );
			}
		}
		
		return new GameListResponse( requestId, games );
	}

	/*************************************************************************************************************************************************************************************************
	 ************************************************************************************************************************************************************************************************* 
	 *************************************************************************************************************************************************************************************************/
	
	/**
	 * Creates an FetchGame request document from an {@link FetchGameRequest} object
	 *
	 * @param request the FetchGameRequest object to be used in generating the XML request document
	 * @return the generated FetchGame request XML document
	 */
	public static Document createFetchGameRequestDocument( FetchGameRequest request )
	{
		Document requestDoc = builder.newDocument( );
		
		DocumentType docType = requestDoc.getImplementation( ).createDocumentType( FetchGameRequest.requestTag, null, CDGRequest.dtdDomain + FetchGameRequest.dtdName );
		
		Element root = requestDoc.createElement( FetchGameRequest.requestTag  );
		
		root.setAttribute( "requestId", request.requestId );
		root.setAttribute( "gameId", request.gameId );
		
		requestDoc.appendChild( docType );
		requestDoc.appendChild( root );
		
		return requestDoc;
	}
	
	/**
	 * Parses an {@link FetchGameRequest} object from an XML request document
	 * 
	 * @param doc the XML request document containing the FetchGameRequest information
	 * @return the parsed FetchGameRequest object
	 * @throws CDGParseException If the request document cannot be parsed
	 */
	public static FetchGameRequest parseFetchGameRequestDocument( Document doc ) throws CDGParseException
	{
		NodeList requestNodes = doc.getElementsByTagName( FetchGameRequest.requestTag );
		
		if( requestNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseFetchGameRequest: cannot parse request. Incorrect number of <" + FetchGameRequest.requestTag + "> tags found." );
		}
		
		Node requestNode = requestNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( requestNode, "requestId", true );
		String gameId = CDGXMLHelper.getProperty( requestNode, "gameId", true );
		
		return new FetchGameRequest( requestId, gameId );
	}
	
	/**
	 * Creates an FetchGame response document from an {@link FetchGameResponse} object
	 *
	 * @param response the FetchGameResponse object to be used in generating the XML response document
	 * @return the generated FetchGame response XML document
	 */
	public static Document createFetchGameResponseDocument( FetchGameResponse response )
	{
		Document responseDoc = builder.newDocument( );
		
		DocumentType docType = responseDoc.getImplementation( ).createDocumentType( FetchGameResponse.responseTag, null, CDGResponse.dtdDomain + FetchGameResponse.dtdName );
		
		Element root = responseDoc.createElement( FetchGameResponse.responseTag  );
		
		root.setAttribute( "requestId", response.requestId );
		
		for( String file : response.list )
		{
			Element fileNode = responseDoc.createElement( "file" );
			fileNode.setAttribute( "name", file );
			root.appendChild( fileNode );
		}
		
		responseDoc.appendChild( docType );
		responseDoc.appendChild( root );
		
		return responseDoc;
	}
	
	/**
	 * Parses an {@link FetchGameResponse} object from an XML response document
	 * 
	 * @param doc the XML response document containing the FetchGameResponse information
	 * @return the parsed FetchGameResponse object
	 * @throws CDGParseException If the response document cannot be parsed
	 */
	public static FetchGameResponse parseFetchGameResponseDocument( Document doc ) throws CDGParseException
	{
		NodeList responseNodes = doc.getElementsByTagName( FetchGameResponse.responseTag );
		
		if( responseNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseFetchGameResponse: cannot parse request. Incorrect number of <" + FetchGameResponse.responseTag + "> tags found." );
		}
		
		Node responseNode = responseNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( responseNode, "requestId", true );
		
		ArrayList< String > files = new ArrayList< String >( );
		
		NodeList childNodes = responseNode.getChildNodes( );
		
		int childNodesLength = childNodes.getLength( );
		
		for( int i = 0; i < childNodesLength; i++ )
		{
			Node childNode = childNodes.item( i );
			if( childNode.getNodeName( ).equals( "file" ) )
			{
				files.add( CDGXMLHelper.getProperty( childNode, "name", true ) );
			}
		}
		
		return new FetchGameResponse( requestId, files );
	}
	
	/*************************************************************************************************************************************************************************************************
	 ************************************************************************************************************************************************************************************************* 
	 *************************************************************************************************************************************************************************************************/
	
	/**
	 * Creates an ValidateRule request document from an {@link ValidateRuleRequest} object
	 *
	 * @param request the ValidateRuleRequest object to be used in generating the XML request document
	 * @return the generated ValidateRule request XML document
	 */
	public static Document createValidateRuleRequestDocument( ValidateRuleRequest request )
	{
		Document requestDoc = builder.newDocument( );
		
		DocumentType docType = requestDoc.getImplementation( ).createDocumentType( ValidateRuleRequest.requestTag, null, CDGRequest.dtdDomain + ValidateRuleRequest.dtdName );
		
		Element root = requestDoc.createElement( ValidateRuleRequest.requestTag );
		
		root.setAttribute( "requestId", request.requestId );
		root.setAttribute( "type", request.type );
		
		String params = "";
		
		boolean firstParam = true;
		
		HashMap< String, String > parameters = request.parameters;
		
		for( String s : parameters.keySet( ) )
		{
			if( !firstParam )
			{
				params += ";";
			}
			firstParam = false;
			
			params += s + ":" + parameters.get( s );
		}

		root.setAttribute( "parameters", params );
		
		root.setTextContent( request.data );
		
		requestDoc.appendChild( docType );
		requestDoc.appendChild( root );
		
		return requestDoc;
	}
	
	/**
	 * Parses an {@link ValidateRuleRequest} object from an XML request document
	 * 
	 * @param doc the XML request document containing the ValidateRuleRequest information
	 * @return the parsed ValidateRuleRequest object
	 * @throws CDGParseException If the request document cannot be parsed
	 */
	public static ValidateRuleRequest parseValidateRuleRequestDocument( Document doc ) throws CDGParseException
	{
		NodeList requestNodes = doc.getElementsByTagName( ValidateRuleRequest.requestTag );
		
		if( requestNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseValidateRuleRequest: cannot parse request. Incorrect number of <" + ValidateRuleRequest.requestTag + "> tags found." );
		}
		
		Node requestNode = requestNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( requestNode, "requestId", true );
		String type = CDGXMLHelper.getProperty( requestNode, "type", true );
		String parameters = CDGXMLHelper.getProperty( requestNode, "parameters", true );
		String data = requestNode.getTextContent( );
		return new ValidateRuleRequest( requestId, type, parameters, data );
	}
	
	/**
	 * Creates an ValidateRule response document from an {@link ValidateRuleResponse} object
	 *
	 * @param response the ValidateRuleResponse object to be used in generating the XML response document
	 * @return the generated ValidateRule response XML document
	 */
	public static Document createValidateRuleResponseDocument( ValidateRuleResponse response )
	{
		Document responseDoc = builder.newDocument( );
		
		DocumentType docType = responseDoc.getImplementation( ).createDocumentType( ValidateRuleResponse.responseTag, null, CDGResponse.dtdDomain + ValidateRuleResponse.dtdName );
		
		Element root = responseDoc.createElement( ValidateRuleResponse.responseTag );
		
		root.setAttribute( "requestId", response.requestId );
		root.setAttribute( "response", Boolean.toString( response.response ) );
		
		responseDoc.appendChild( docType );
		responseDoc.appendChild( root );
		
		return responseDoc;
	}
	
	/**
	 * Parses an {@link ValidateRuleResponse} object from an XML response document
	 * 
	 * @param doc the XML response document containing the ValidateRuleResponse information
	 * @return the parsed ValidateRuleResponse object
	 * @throws CDGParseException If the response document cannot be parsed
	 */
	public static ValidateRuleResponse parseValidateRuleResponseDocument( Document doc ) throws CDGParseException
	{
		NodeList responseNodes = doc.getElementsByTagName( ValidateRuleResponse.responseTag );
		
		if( responseNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseValidateRuleResponse: cannot parse request. Incorrect number of <" + ValidateRuleResponse.responseTag + "> tags found." );
		}
		
		Node responseNode = responseNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( responseNode, "requestId", true );
		boolean response = Boolean.parseBoolean( CDGXMLHelper.getProperty( responseNode, "response", true ) );
		return new ValidateRuleResponse( requestId, response );
	}
	
	/*************************************************************************************************************************************************************************************************
	 ************************************************************************************************************************************************************************************************* 
	 *************************************************************************************************************************************************************************************************/
	
	/**
	 * Creates an UpdateProgress request document from an {@link UpdateProgressRequest} object
	 * 
	 * @param request the UpdateProgressRequest object to be used in generating the XML request document 
	 * @return the generated UpdateProgress request XML document
	 */
	public static Document createUpdateProgressRequestDocument( UpdateProgressRequest request )
	{
		Document requestDoc = builder.newDocument( );
		
		DocumentType docType = requestDoc.getImplementation( ).createDocumentType( UpdateProgressRequest.requestTag, null, CDGRequest.dtdDomain + UpdateProgressRequest.dtdName );
		
		Element root = requestDoc.createElement( UpdateProgressRequest.requestTag );
		
		root.setAttribute( "requestId", request.requestId );
		root.setAttribute( "username", request.username );
		root.setAttribute( "password", request.password );
		root.setAttribute( "gameId", request.gameId );
		root.setAttribute( "completedTask", request.completedTask );
		
		requestDoc.appendChild( docType );
		requestDoc.appendChild( root );
		
		return requestDoc;
	}
	
	/**
	 * Parses an {@link UpdateProgressRequest} object from an XML request document
	 * 
	 * @param doc the XML request document containing the UpdateProgressRequest information
	 * @return the parsed UpdateProgressRequest object
	 * @throws CDGParseException If the request document cannot be parsed
	 */
	public static UpdateProgressRequest parseUpdateProgressRequestDocument( Document doc ) throws CDGParseException
	{
		NodeList requestNodes = doc.getElementsByTagName( UpdateProgressRequest.requestTag );
		
		if( requestNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseUpdateProgressRequest: cannot parse request. Incorrect number of <" + UpdateProgressRequest.requestTag + "> tags found." );
		}
		
		Node requestNode = requestNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( requestNode, "requestId", true );
		String username = CDGXMLHelper.getProperty( requestNode, "username", true );
		String password = CDGXMLHelper.getProperty( requestNode, "password", true );
		String gameId = CDGXMLHelper.getProperty( requestNode, "gameId", true );
		String completedTask = CDGXMLHelper.getProperty( requestNode, "completedTask", true );
		return new UpdateProgressRequest( requestId, username, password, gameId, completedTask );
	}
	
	/**
	 * Creates an UpdateProgress response document from an {@link UpdateProgressResponse} object
	 * 
	 * @param response the UpdateProgressResponse object to be used in generating the XML response document 
	 * @return the generated UpdateProgress response XML document
	 */
	public static Document createUpdateProgressResponseDocument( UpdateProgressResponse response )
	{
		Document responseDoc = builder.newDocument( );
		
		DocumentType docType = responseDoc.getImplementation( ).createDocumentType( UpdateProgressResponse.responseTag, null, CDGResponse.dtdDomain + UpdateProgressResponse.dtdName );
		
		Element root = responseDoc.createElement( UpdateProgressResponse.responseTag );
		
		root.setAttribute( "requestId", response.requestId );
		root.setAttribute( "response", Boolean.toString( response.response ) );
		
		responseDoc.appendChild( docType );
		responseDoc.appendChild( root );
		
		return responseDoc;
	}
	
	/**
	 * Parses an {@link UpdateProgressResponse} object from an XML response document
	 * 
	 * @param doc the XML response document containing the UpdateProgressResponse information
	 * @return the parsed UpdateProgressResponse object
	 * @throws CDGParseException If the response document cannot be parsed
	 */
	public static UpdateProgressResponse parseUpdateProgressResponseDocument( Document doc ) throws CDGParseException
	{
		NodeList responseNodes = doc.getElementsByTagName( UpdateProgressResponse.responseTag );
		
		if( responseNodes.getLength( ) != 1 )
		{
			throw new CDGParseException( "parseUpdateProgressResponse: cannot parse request. Incorrect number of <" + UpdateProgressResponse.responseTag + "> tags found." );
		}
		
		Node responseNode = responseNodes.item( 0 );
		
		String requestId = CDGXMLHelper.getProperty( responseNode, "requestId", true );
		boolean response = Boolean.parseBoolean( CDGXMLHelper.getProperty( responseNode, "response", true ) );
		return new UpdateProgressResponse( requestId, response );
	}
}
